
----------------------------------------------------------------
--- Copyright：  迷你玩-海马
--- Description：数据缓存服务器
----------跨服务器的基于共享内存shm(share_dict)数据cache层------
----------将数据从逻辑服中分离出来------------------------------
----------数据不会写落地，重启或者stop，所有数据会被清理--------
----------reload操作会保留之前的数据---------------------------
----------------------------------------------------------------

---接口
---read    读取数据 (默认加远程锁 可选不加)   key+hash  get value
---save    保存数据 (默认解远程锁 可选不解)   key+hash  set value
---del     删除数据 (别名hdel)

---mread   批量读取数据                      (key1+hash1 key2+hash2 key3+hash3)

---hlen           获得hash的参数
---hlist          按key获取所有hash值，不带有数据
---hget_all       按key获取所有hash数据

---clean_cache    直接清理cache

---unlock_kh      主动解一个远程锁

---comm    通用复合接口
---comm2   测试接口

------------------------------------------------------------------
---  key hash 的数据结构：
---		key 123456
---			hash=1    value=vv1
---			hash=2    value=vv2
---			hash=3    value=vv3
-------------------------------------------------------------------


local assert     = assert
local require    = require
local bit        = bit
local coroutine  = coroutine
local debug      = debug
local io         = io
local pairs      = pairs
local ipairs     = ipairs
local math       = math
local os         = os
local print      = print
local pcall      = pcall
local xpcall     = xpcall
local rawget     = rawget
local rawset     = rawset
local select     = select
local string     = string
local table      = table
local tonumber   = tonumber
local tostring	 = tostring
local error      = error
local type       = type
local unpack     = unpack
local setmetatable = setmetatable
local getmetatable = getmetatable
local ngx = ngx

local string_char  = string.char
local string_sub   = string.sub
local string_byte  = string.byte
local string_gsub  = string.gsub
local string_find  = string.find
local string_upper = string.upper

local math_fmod   = math.fmod
local math_floor  = math.floor
local math_pow    = math.pow
local math_abs    = math.abs


local uu           = require( "modules.commhm.utils" )
local log          = require( "modules.commhm.log" )
local ns_time      = require( "modules.commhm.time" )
local ns_network   = require( "modules.commhm.network" )
local ns_http      = require( "modules.commhm.resty.http" )

local ns_const_vars  = require("modules.commhm.const_vars")

local ns_config       = require( "modules.data_cache.data_cache_config" )
local gg              = require( "modules.data_cache.data_cache_shm_gg" )
local ns_auth_checker = require( "modules.data_cache.auth_checker" )

local ns_share_dict_checker = require("modules.commhm.framework.share_dict_checker");

local ns_func_ext = require( "modules.data_cache.data_cache_shm_func_ext" )


local NGX_NULL  = "not_found"                  --ngx.null


local FOO = {
	_VERSION = '0.12.1',
	cmds = {},

	worker_id = ngx.worker.id(),
};



FOO.enter = function()
	ngx.ctx.m_params = ngx.req.get_uri_args();

	ngx.header["Carry"] = ngx.ctx.m_params.carry   --防止网络请求串流

	--log.debug( "headers1= " .. table.tostring(ngx.req.get_headers()) );

	local cmd_ = ngx.ctx.m_params.cmd
	if  cmd_  and FOO.cmds[cmd_]  then
		if  ns_auth_checker.check_data_cache_auth() then
			gg.init()
			FOO.cmds[cmd_]()
			gg.final()
		else
			log.error("auth error:" .. (cmd_ or 'nil') )
		end
	else
		log.error("action error:" .. (cmd_ or 'nil') )
	end

	--log.debug( "headers2= " .. table.tostring(ngx.resp.get_headers()) );

end



-----------------------------通用接口----------------
---POST
FOO.cmds.comm = function()
	log.debug( "call dc - comm" )

	--参数在post里
	ngx.req.read_body();
	local args_ = ngx.req.get_post_args();
	uu.var_dump( ngx.ctx.m_params )

	--合并参数
	for k, v in pairs( ngx.ctx.m_params ) do
		args_[k] = v
	end

	ngx.ctx.m_params     = args_
	ngx.ctx.m_params.cmd = ngx.ctx.m_params.func   --顶掉comm

	uu.var_dump( ngx.ctx.m_params )

	if  FOO.cmds[ ngx.ctx.m_params.cmd ] then
		log.debug( "call dc - " .. ngx.ctx.m_params.cmd )
		FOO.cmds[ ngx.ctx.m_params.cmd ]()
	else
		log.error("comm action error:" .. (ngx.ctx.m_params.cmd or 'nil') )
	end

end


--GET 用来测试的接口
FOO.cmds.comm2 = function()
	log.debug( "call dc - comm2" )
	--参数在get里
	ngx.ctx.m_params.cmd = ngx.ctx.m_params.func   --顶掉comm

	uu.var_dump( ngx.ctx.m_params )

	if  FOO.cmds[ ngx.ctx.m_params.cmd ] then
		log.debug( "call dc - " .. ngx.ctx.m_params.cmd )
		FOO.cmds[ ngx.ctx.m_params.cmd ]()
	end
end



-----------------------------------------------------------------
--存储数据
--_M.save = function( self, shm_name, key, hash, value, et_, err_call_back, shm_delay_ )
FOO.cmds.save = function()

	local args_ = ngx.ctx.m_params

	local name_   = args_.name      --shm name: ma map posting
	local key_    = args_.key
	local hash_   = args_.hash
	local select_ = gg.private_get_select()
	local et_     = args_.et        --expire_time

	local len_
	local value_
	if  args_.value then
		value_ = args_.value   --一定是string
	elseif args_.post_value then
		--post里的所有内容都是value, post_value是长度
		len_ = tonumber(args_.post_value) or 0
		ngx.req.read_body();
		value_ = ngx.req.get_body_data()
	else
		--post里
		ngx.req.read_body();
		local args = ngx.req.get_post_args();
		uu.var_dump( args )

		value_= args["value"]
		if  value_ then
			if  tonumber(args["len"]) == #value_ then
				log.day_list( "dc_shm", "dc_client", "server_post_len_ok",    args["len"] )
			else
				log.day_list( "dc_shm", "dc_client", "server_post_len_error", args["len"], #value_ )
			end
		else
			uu.return_string( "NO_V", ns_const_vars.ERROR_CODE.PARAM )
			return
		end
	end

	if  len_ and (len_ <= 0) then
		uu.return_string( "NO_V_LEN0", ns_const_vars.ERROR_CODE.PARAM )
		return
	end


	---if decode base64
	if  args_.isbase64 == '1' then
		local value_64_ = ngx.decode_base64(value_)
		if  value_64_ then
			log.day_list_simple( "dc_shm", "base64_decode_ok", #value_, #value_64_ )
			value_ = value_64_
		else
			log.day_list_simple( "dc_shm", "error_base64", key_, hash_, select_, value_ )
			uu.return_string( "ERROR_BASE64", ns_const_vars.ERROR_CODE.PARAM )
			return
		end
	end

	---check vtype
	if  args_.vtype then
		-- 格式能校验通过，就不用校验len
		local pass_ = false
		if     args_.vtype == 'json' then
			if  uu.stringIsJson( value_ ) then
				pass_ = true
			end
		elseif args_.vtype == 'lua' then
			if  uu.stringIsLuaTable( value_ ) then
				pass_ = true
			end
		end
		if  not pass_ then
			log.day_list( "dc_shm", "error_post_value_len", #value_, args_.vtype, '\n', value_ )
			uu.return_string( "POST_VALUE_LEN_ERROR", ns_const_vars.ERROR_CODE.PARAM )
			return
		end
	else
		-- 需要校验len
		if  len_ and (not (#value_ == len_)) then
			log.day_list( "dc_shm", "dc_client", "post_value_len_error", len_, #value_ )
		end
	end


	if  not ( name_ and select_ and key_ and hash_ and value_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local shm_mgr_ = gg.shmMgrDcList[ name_ ]
	if  shm_mgr_ then
		local ret_ = shm_mgr_:API_shm_save( select_, key_, hash_, value_, et_,
				gg.error_exit, args_.read_only )
		uu.return_string( ret_ or "" )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf name not exit=" .. name_ )
	end

end


-----------------------------------------------------------------
--获取数据
--_M.read = function( self, shm_name, key, hash, err_call_back )
FOO.cmds.read = function()

	local args_ = ngx.ctx.m_params

	local name_   = args_.name      --shm name: ma map posting
	local key_    = args_.key
	local hash_   = args_.hash
	local stale_  = args_.stale
	local select_ = gg.private_get_select()

	if  args_.no_cache == 'y' then
		--log.day_list_short( "dc_shm", "read_no_shared_cache", name_, select_, key_, hash_ );
		ngx.ctx.no_shared_cache = 'y'   --不存shared缓存
	end

	if  not ( name_ and select_ and key_ and hash_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local shm_mgr_ = gg.shmMgrDcList[ name_ ]
	if  shm_mgr_ then
		local ret_ = shm_mgr_:API_shm_read( select_, key_, hash_, stale_,
				gg.error_exit, args_.read_only )
		if  ret_ then
			uu.return_string( ret_ )
			if  #ret_ <= 0 then
				log.day_list_simple( "dc_shm", "error_read_len0", select_, key_, hash_, ret_ )
			end
		end

	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end



--批量获得数据
-- _M.mread = function( self, shm_name, all_kv_list, err_call_back )
FOO.cmds.mread = function()

	local args_   = ngx.ctx.m_params

	local name_   = args_.name      --shm name: ma map posting
	local stale_  = args_.stale
	local select_ = gg.private_get_select()

	if  not ( name_ and select_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	--local key_hash_ = all_kv_list[i].key .. "_h" .. all_kv_list[i].hash
	local all_kv_list
	if  args_.kv_list then
		local t_ = uu.split( args_.kv_list, "-" )

		uu.var_dump( t_ )

		if  #t_ > 0 then
			all_kv_list = {}
			for i=1, #t_, 2 do
				all_kv_list[ #all_kv_list + 1 ] = { key=t_[i],  hash=t_[i+1] }
			end
		end

		uu.var_dump( all_kv_list )

	else
		--read post
		ngx.req.read_body();
		local args = ngx.req.get_post_args();
		uu.var_dump( args )
		if  args['kv_list'] then
			all_kv_list = uu.safe_loadstring ( args['kv_list'] )
		end
	end

	if (not  all_kv_list) or #all_kv_list <= 0 then
		uu.return_string( "PARAM_ERROR_LIST0", ns_const_vars.ERROR_CODE.PARAM )
		return
	end


	local shm_mgr_ = gg.shmMgrDcList[ name_ ]
	if  shm_mgr_ then
		local ret_ = shm_mgr_:API_shm_mread( select_, all_kv_list, stale_, gg.error_exit )
		uu.var_dump( ret_ )
		uu.return_table( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end


-- 删除一个key下的一个hash
--_M.del = function( self, shm_name, key, hash, err_call_back )
FOO.cmds.del = function()

	local args_   = ngx.ctx.m_params

	local name_   = args_.name      --shm name: ma map posting
	local key_    = args_.key
	local hash_   = args_.hash
	local select_ = gg.private_get_select()

	if  not ( name_ and select_ and key_ and hash_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local shm_mgr_ = gg.shmMgrDcList[ name_ ]
	if  shm_mgr_ then
		local ret_ = shm_mgr_:API_shm_del( select_, key_, hash_, gg.error_exit )
		uu.return_string( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end

FOO.cmds.hdel = FOO.cmds.del ---别名



--加锁
FOO.cmds.lock_kh = function()
	local args_   = ngx.ctx.m_params
	local name_   = args_.name      --shm name: ma map posting
	local select_ = gg.private_get_select()

	local kh_
	if  args_.kh then
		kh_     = args_.kh
	elseif  args_.key and  args_.hash then
		kh_     = args_.key .. '_h' .. args_.hash      --key .. "_h" .. hash
	end

	if  not ( name_ and select_ and kh_ ) then
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		return
	end

	local shm_mgr_ = gg.shmMgrDcList[ name_ ]
	if  shm_mgr_ then
		local ret_, err_ = shm_mgr_:API_shm_try_get_resty_lock( select_, kh_, gg.error_exit )
		if  err_ then
			uu.return_table( { ret=ret_, msg=err_ } )
		else
			uu.return_table( { ret=0,    lock_kh_id=ret_ } )
		end
	else
		uu.return_table( {ret=ns_const_vars.ERROR_CODE.PARAM, msg="PARAM_ERROR" } )
		log.error( "conf not exit=" .. name_ )
	end

end



--解锁
FOO.cmds.unlock_kh = function()
	local args_   = ngx.ctx.m_params
	local name_   = args_.name      --shm name: ma map posting
	local select_ = gg.private_get_select()

	local kh_
	if  args_.kh then
		kh_     = args_.kh
	elseif  args_.key and  args_.hash then
		kh_     = args_.key .. '_h' .. args_.hash      --key .. "_h" .. hash
	end

	if  not ( name_ and select_ and kh_ ) then
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		return
	end

	local shm_mgr_ = gg.shmMgrDcList[ name_ ]
	if  shm_mgr_ then
		local ret_ = shm_mgr_:API_shm_unlock_resty_lock( select_, kh_, FOO.error_exit )
		uu.return_string( ret_ )
	else
		uu.return_string( "PARAM_ERROR", ns_const_vars.ERROR_CODE.PARAM )
		log.error( "conf not exit=" .. name_ )
	end

end



-- 写入数据中的一个path,目前最大支持3层 (业务强相关代码)
FOO.cmds.save_by_path = ns_func_ext.save_by_path



------------初始化
FOO.init = function()

	---定时检查share_dict剩余空间
	local shared_dict_name_list_ = { "miniw", "ssdb", }
	for i=0, 9 do
		shared_dict_name_list_[ #shared_dict_name_list_ + 1 ] = "ssdb"  .. i
		shared_dict_name_list_[ #shared_dict_name_list_ + 1 ] = "miniw" .. i
	end

	if  ns_config.shm then
		for k, v in pairs( ns_config.shm ) do
			for i=0, 9 do
				if  type(k) == 'string' then
					shared_dict_name_list_[ #shared_dict_name_list_ + 1 ] = k .. i
				end
			end
		end
	end
	ns_share_dict_checker.start_check_by_timer( shared_dict_name_list_, 604, "dc4" )

end


FOO.init()
return FOO;
